package work.linruchang.util;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.lang.func.Func0;
import cn.hutool.core.lang.func.Func1;
import cn.hutool.core.lang.func.LambdaUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.*;
import cn.hutool.extra.spring.SpringUtil;
import lombok.SneakyThrows;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.util.ResourceUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.StandardServletEnvironment;
import work.linruchang.util.consts.FileTypeEnum;
import work.linruchang.util.consts.FuzzyModeEnum;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 增强的Spring处理工具
 *
 * @author LinRuChang
 * @version 1.0
 * @date 2022/04/02
 * @since 1.8
 **/
public class EnhanceSpringUtil extends SpringUtil {

    private static DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader();

    /**
     * 获取当前请求
     *
     * @return
     */
    public static HttpServletRequest getCurrrentRequest() {
        return Optional.ofNullable((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .map(ServletRequestAttributes::getRequest)
                .orElseThrow(() -> new RuntimeException("非Web应用，请检查"));
    }

    /**
     * 获取当前请求的请求方式
     * @return
     */
    public static String getCurrrentRequestMethod() {
        return getCurrrentRequest().getMethod();
    }

    /**
     * 获取当前请求的会话ID
     * @return
     */
    public static String getCurrentRequestSessionId() {
        return getCurrrentRequest().getSession().getId();
    }

    /**
     * 获取当前响应
     *
     * @return
     */
    public static HttpServletResponse getCurrentResponse() {
        return Optional.ofNullable((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .map(ServletRequestAttributes::getResponse)
                .orElseThrow(() -> new RuntimeException("非Web应用，请检查"));
    }

    /**
     * 获取当前请求表单、url参数
     * @return
     */
    @SneakyThrows
    public static Dict getCurrentRequestParams() {
        HttpServletRequest currrentRequest = getCurrrentRequest();
        Dict params = MapUtil.emptyIfNull(currrentRequest.getParameterMap())
                .entrySet().stream()
                .collect(Collectors.toMap(paramKeyValues -> paramKeyValues.getKey(), paramKeyValues -> paramKeyValues.getValue()[0], (t1, t2) -> t2, () -> Dict.create()));

        //填充文件上传的参数
        Collection<Part> parts = currrentRequest.getParts();
        Class standardMultipartFileClazz = EnhanceClassUtil.forName("org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile");
        Constructor constructor = ReflectUtil.getConstructor(standardMultipartFileClazz, Part.class, String.class);
        for(Part part : parts) {
            String keyName = part.getName();
            if(!params.containsKey(keyName)) {
                Object standardMultipartFileBean = constructor.newInstance(part, part.getSubmittedFileName());
                params.put(keyName,standardMultipartFileBean);
            }
        }

        return params;
    }

    /**
     * 获取当前请求表单、url参数
     * @return
     */
    public static <T> T getCurrentRequestParams(Class<T> resultBeanClazz) {
        return getCurrentRequestParams().toBeanIgnoreCase(resultBeanClazz);
    }

    /**
     * 获取当前请求某个表单、url参数
     * @return
     */
    public static <T> T getCurrentRequestParam(String paramName, Class<T> resultTypeClazz) {
        return getCurrentRequestParams().getByPath(paramName, resultTypeClazz);
    }

    /**
     * 获取当前请求某个表单、url参数
     * @return
     */
    public static String getCurrentRequestParam(String paramName) {
        return getCurrentRequestParams().getByPath(paramName, String.class);
    }

    /**
     * 获取当前请求某个表单、url参数
     * @return
     */
    public static <T, M> M getCurrentRequestParam(Func1<T, M> paramName) {
        String fieldName = LambdaUtil.getFieldName(paramName);
        Class<M> methodResultType = EnhanceLambdaUtil.getMethodResultType(paramName);
        return getCurrentRequestParam(fieldName,methodResultType);
    }


    /**
     * 获取获取当前请求头
     * @return
     */
    public static Dict getCurrentRequestHeaders() {
        HttpServletRequest currrentRequest = getCurrrentRequest();
        Dict headerKeyValues = Dict.create();
        Enumeration<String> headerNames = currrentRequest.getHeaderNames();
        while(headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            headerKeyValues.put(headerName, currrentRequest.getHeader(headerName));
            headerKeyValues.put(headerName.toLowerCase(), currrentRequest.getHeader(headerName));
            headerKeyValues.put(headerName.toUpperCase(), currrentRequest.getHeader(headerName));

            List<String> headerNameList = StrUtil.split(headerName, StrUtil.DASHED);
            String dashUpperHeaderName = headerNameList.stream()
                    .map(StrUtil::upperFirst)
                    .collect(Collectors.joining(StrUtil.DASHED));
            String dashLowerHeaderName = headerNameList.stream()
                    .map(StrUtil::lowerFirst)
                    .collect(Collectors.joining(StrUtil.DASHED));
            headerKeyValues.put(dashLowerHeaderName, currrentRequest.getHeader(headerName));
            headerKeyValues.put(dashUpperHeaderName, currrentRequest.getHeader(headerName));
        }
        return headerKeyValues;
    }

    /**
     * 获取获取当前某个请求头
     * @param headerName
     * @param headerValueType
     * @return
     * @param <T>
     */
    public static <T> T getCurrentRequestHeader(String headerName, Class<T> headerValueType) {
        return getCurrentRequestHeaders().getByPath(headerName,headerValueType);
    }

    /**
     * 获取获取当前某个请求头
     * @param headerName
     * @return
     */
    public static String getCurrentRequestHeader(String headerName) {
        return getCurrentRequestHeader(headerName,String.class);
    }

    /**
     * 获取获取当前某个请求头
     * @param headerName
     * @return
     * @param <T>
     * @param <M>
     */
    public static <T, M> M  getCurrentRequestHeader(Func1<T, M> headerName) {
        String fieldName = LambdaUtil.getFieldName(headerName);
        Class<M> methodResultType = EnhanceLambdaUtil.getMethodResultType(headerName);
        return getCurrentRequestHeader(fieldName,methodResultType);
    }


    /**
     * 获取当前请求属性
     * @return
     */
    public static Dict getCurrentRequestAttributes() {
        HttpServletRequest currrentRequest = getCurrrentRequest();
        Dict attributeKeyValues = Dict.create();
        Enumeration<String> attributeNames = currrentRequest.getAttributeNames();
        while (attributeNames.hasMoreElements()) {
            String attributeName = attributeNames.nextElement();
            attributeKeyValues.put(attributeName,currrentRequest.getAttribute(attributeName));
            attributeKeyValues.put(attributeName.toLowerCase(),currrentRequest.getAttribute(attributeName));
            attributeKeyValues.put(attributeName.toUpperCase(),currrentRequest.getAttribute(attributeName));
        }
        return attributeKeyValues;
    }

    /**
     * 获取当前请求某个属性
     * @param attributeName
     * @param attributeValueType
     * @return
     * @param <T>
     */
    public static <T> T getCurrentRequestAttribute(String attributeName,Class<T> attributeValueType) {
        return getCurrentRequestAttributes().getByPath(attributeName,attributeValueType);
    }

    /**
     * 获取当前请求某个属性
     * @param attributeName
     * @return
     */
    public static String getCurrentRequestAttribute(String attributeName) {
        return getCurrentRequestAttribute(attributeName,String.class);
    }

    /**
     * 获取当前请求某个属性
     * @param attributeName
     * @return
     * @param <T>
     * @param <M>
     */
    public static <T, M> M  getCurrentRequestAttribute(Func1<T, M> attributeName) {
        String fieldName = LambdaUtil.getFieldName(attributeName);
        Class<M> methodResultType = EnhanceLambdaUtil.getMethodResultType(attributeName);
        return getCurrentRequestAttribute(fieldName,methodResultType);
    }



    /**
     * 获取当前请求会话session内的属性
     *
     * @return
     */
    public static Dict getCurrentSessionAttributes() {
        HttpServletRequest currrentRequest = getCurrrentRequest();
        Dict attributeKeyValues = Dict.create();
        Enumeration<String> attributeNames = currrentRequest.getSession().getAttributeNames();
        while (attributeNames.hasMoreElements()) {
            String attributeName = attributeNames.nextElement();
            attributeKeyValues.put(attributeName,currrentRequest.getAttribute(attributeName));
            attributeKeyValues.put(attributeName.toLowerCase(),currrentRequest.getAttribute(attributeName));
            attributeKeyValues.put(attributeName.toUpperCase(),currrentRequest.getAttribute(attributeName));
        }
        return attributeKeyValues;
    }

    /**
     * 获取当前请求会话session内某个属性
     * @param attributeName
     * @param attributeValueType
     * @return
     * @param <T>
     */
    public static <T> T getCurrentSessionAttribute(String attributeName,Class<T> attributeValueType) {
        return getCurrentSessionAttributes().getByPath(attributeName,attributeValueType);
    }

    /**
     * 获取当前请求会话session内某个属性
     * @param attributeName
     * @return
     */
    public static String getCurrentSessionAttribute(String attributeName) {
        return getCurrentSessionAttribute(attributeName,String.class);
    }

    /**
     * 获取当前请求会话session内某个属性
     * @param attributeName
     * @return
     * @param <T>
     * @param <M>
     */
    public static <T, M> M  getCurrentSessionAttribute(Func1<T, M> attributeName) {
        String fieldName = LambdaUtil.getFieldName(attributeName);
        Class<M> methodResultType = EnhanceLambdaUtil.getMethodResultType(attributeName);
        return getCurrentRequestAttribute(fieldName,methodResultType);
    }



    /**
     * 命令浏览器删除某个cookie
     *
     * @param cookieName
     * @return
     */
    public static Cookie delCookie(String cookieName) {
        Assert.notBlank(cookieName, "cookie名不能缺失，请检查");

        Cookie cookie = new Cookie(cookieName, null);
        cookie.setPath("/");
        cookie.setMaxAge(0);

        Optional.ofNullable(getCurrentResponse())
                .ifPresent(response -> response.addCookie(cookie));
        return cookie;
    }

    /**
     * 添加Cookie
     * @param cookieName 待添加的Cookie
     * @param expireSecondLong cookie过期时间长度（秒）
     * @return
     * @param <R>
     */
    public static <R> Cookie addCookie(Func0<R> cookieName, Integer expireSecondLong ) {
        String fieldName = LambdaUtil.getFieldName(cookieName);
        String fieldValue = Convert.toStr(cookieName.callWithRuntimeException());
        return addCookie(fieldName, fieldValue,expireSecondLong);
    }
    public static <R> Cookie addCookie(Func0<R> cookieName) {
        return addCookie(cookieName,null);
    }

    public static <T, R> Cookie addCookie(Func1<T, R> cookieName, String cookieValue) {
        return addCookie(cookieName,cookieValue,null);
    }
    public static <T, R> Cookie addCookie(Func1<T, R> cookieName, String cookieValue, Integer expireSecondLong) {
        return addCookie(LambdaUtil.getFieldName(cookieName), cookieValue,expireSecondLong);
    }



    public static Cookie addCookie(String cookieName, String cookieValue) {
        return addCookie(cookieName, cookieValue, null);
    }

    /**
     * 添加Cookie
     * @param cookieName cookie名
     * @param cookieValue cookie值
     * @param expireSecondLong cookie过期时间长度（秒）
     * @return
     */
    public static Cookie addCookie(String cookieName, String cookieValue, Integer expireSecondLong) {
        Cookie cookie = null;
        if (StrUtil.isAllNotBlank(cookieName, cookieValue)) {
            HttpServletResponse currentResponse = getCurrentResponse();
            cookie = new Cookie(cookieName, cookieValue);
            if(expireSecondLong != null) {
                cookie.setMaxAge(expireSecondLong);
            }
            currentResponse.addCookie(cookie);
        }
        return cookie;
    }


    public static List<Cookie> addCookie(Object bean) {
        return addCookie(bean,null);
    }

    /**
     * 对象属性都添加进入本地cookie
     *
     * @param bean
     * @return
     */
    public static List<Cookie> addCookie(Object bean, Integer expireSecondLong) {
        final List cookieList = CollUtil.newArrayList();
        if (ObjectUtil.isNotEmpty(bean)) {
            boolean primitiveFlag = bean.getClass().isPrimitive();
            if (BooleanUtil.isFalse(primitiveFlag)) {
                Map<String, Object> keyValueMap = BeanUtil.beanToMap(bean, false, true);
                keyValueMap.forEach((key, value) -> {
                    if (ClassUtil.isBasicType(value.getClass()) || value.getClass() == String.class) {
                        cookieList.add(addCookie(key, Convert.toStr(value),expireSecondLong));
                    }
                });
            }
        }
        return cookieList;
    }

    /**
     * 获取当前请求的URL前缀
     * @return
     */
    public static String getCurrentContextUrl() {
        HttpServletRequest currrentRequest = getCurrrentRequest();
        String contextPath = StrUtil.format("{}://{}:{}/{}", currrentRequest.getScheme(), currrentRequest.getServerName(), currrentRequest.getServerPort(), currrentRequest.getContextPath());
        contextPath = StrUtil.removeSuffix(contextPath, "/");
        return contextPath;
    }



    /**
     * 获取Spring环境变量
     * @param key 键名 （右模糊查询）
     * @return
     */
    private static Dict getEnvironmentInfoByRight(String key) {
        return getEnvironmentInfo(key, FuzzyModeEnum.RIGHT);
    }
    private static Dict getEnvironmentInfoByPrefix(String key) {
        return getEnvironmentInfo(key,FuzzyModeEnum.RIGHT);
    }

    /**
     * 获取Spring环境变量
     * @param key 键名 （左模糊查询）
     * @return
     */
    private static Dict getEnvironmentInfoByLeft(String key) {
        return getEnvironmentInfo(key,FuzzyModeEnum.LEFT);
    }

    /**
     * 获取Spring环境变量
     * @param key 键名 （全模糊查询）
     * @return
     */
    private static Dict getEnvironmentInfoByAll(String key) {
        return getEnvironmentInfo(key,FuzzyModeEnum.ALL);
    }

    /**
     * 获取Spring环境变量
     * @param key 键名
     * @param fuzzyModeEnum 模糊模式
     * @return
     */
    private static Dict getEnvironmentInfo(String key, FuzzyModeEnum fuzzyModeEnum) {
        Dict result = Dict.create();
        StandardServletEnvironment environment = (StandardServletEnvironment) getBean(Environment.class);
        MutablePropertySources propertySources = environment.getPropertySources();
        propertySources.stream().forEach(propertySource -> {
            Object source = propertySource.getSource();
            if(source instanceof Map) {
                Map sourceMap = (Map) source;
                Set<Map.Entry> sourceMapEntrys = sourceMap.entrySet();
                sourceMapEntrys.forEach(sourceEntry -> {
                    String keyName = Convert.toStr(sourceEntry.getKey());
                    Object keyValue = sourceEntry.getValue();
                    switch (fuzzyModeEnum) {
                        case ALL: {
                            if(StrUtil.contains(keyName, key)) {
                                result.put(keyName, keyValue);
                            }
                            break;
                        }
                        case LEFT: {
                            if(StrUtil.endWith(keyName, key)) {
                                result.put(keyName, keyValue);
                            }
                            break;
                        }
                        case RIGHT: {
                            if(StrUtil.startWith(keyName, key)) {
                                result.put(keyName, keyValue);
                            }
                            break;
                        }
                    }
                });
            }
        });

        return result;
    }

    /**
     * 获取数组类型的值
     *
     * @param key      键名
     * @param elemType 结果元素类型
     * @param <T>
     * @return
     */
    public static <T> List<T> getEnvironmentInfoByArrayValue(String key, Class<T> elemType) {
        Dict environmentInfoDict = getEnvironmentInfo(key, FuzzyModeEnum.RIGHT);

        Class<T> resultElemType = (Class<T>) ObjUtil.defaultIfNull(elemType, String.class);


        Set<String> environmentInfoKeys = environmentInfoDict.keySet();

        List<T> result = environmentInfoKeys.stream()
                .filter(environmentInfoKey -> StrUtil.equalsAnyIgnoreCase(StrUtil.subBefore(environmentInfoKey, StrUtil.C_BRACKET_START, false), key))
                .map(environmentInfoKey -> environmentInfoDict.get(environmentInfoKey))
                .map(environmentInfoValue -> Convert.convert(resultElemType, environmentInfoValue))
                .collect(Collectors.toList());


        return result;
    }

    /**
     * 获取数组类型的值
     *
     * @param key 键名
     * @return
     */
    public static List<String> getEnvironmentInfoByArrayValue(String key) {
        return getEnvironmentInfoByArrayValue(key, String.class);
    }


    /**
     * 精确获取某个属性变量
     *
     * @param key          键名
     * @param resultType   键值类型
     * @param defaultValue 键值默认值
     * @param <T>
     * @return
     */
    public static <T> T getEnvironmentInfo(String key, Class<T> resultType, T defaultValue) {
        return SpringUtil.getBean(Environment.class).getProperty(key, resultType, defaultValue);
    }

    public static <T> T getEnvironmentInfo(String key, Class<T> resultType) {
        return Convert.convert(resultType, getEnvironmentInfo(key));
    }

    public static Object getEnvironmentInfo(String key) {
        return getEnvironmentInfo(key, null, null);
    }


    /**
     * 获取Spring全部环境变量
     *
     * @return
     */
    public static Dict getEnvironmentInfos() {
        Dict result = Dict.create();
        StandardServletEnvironment environment = (StandardServletEnvironment) getBean(Environment.class);
        MutablePropertySources propertySources = environment.getPropertySources();
        propertySources.stream().forEach(propertySource -> {
            Object source = propertySource.getSource();
            if (source instanceof Map) {
                Map sourceMap = (Map) source;
                Set<Map.Entry> sourceMapEntrys = sourceMap.entrySet();
                sourceMapEntrys.forEach(sourceEntry -> {
                    String keyName = Convert.toStr(sourceEntry.getKey());
                    Object keyValue = sourceEntry.getValue();
                    keyValue = Convert.toStr(keyValue);

                    result.set(keyName,keyValue);
                });
            }
        });

        return result;
    }


    /**
     * 第一个存在的资源
     *
     * @param springFilePaths Spring格式的Path路径 参考{@link ResourceUtils#CLASSPATH_URL_PREFIX}
     * @return
     */
    @SneakyThrows
    public static Resource firstExistResoure(List<String> springFilePaths) {
        Resource resultResource = CollUtil.emptyIfNull(springFilePaths)
                .stream()
                .filter(StrUtil::isNotBlank)
                .map(defaultResourceLoader::getResource)
                .filter(Resource::exists)
                .findFirst()
                .orElse(null);

        return resultResource;
    }


    /**
     * 不存在则创建（入参两个参数都不得为空）
     * @param springFilePath Spirng形式的文件路径
     * @param fileTypeEnum 文件类型
     * @return
     */
    @SneakyThrows
    public static Resource noExistToTouch(String springFilePath, FileTypeEnum fileTypeEnum) {
        if(ObjUtil.hasEmpty(springFilePath, fileTypeEnum)) {
            return null;
        }

        Resource resource = defaultResourceLoader.getResource(springFilePath);
        if(resource.exists()) {
            return resource;
        }

        File resourceFile = resource.getFile();
        switch (fileTypeEnum) {
            case TEXT_FILE: {
                FileUtil.touch(resourceFile);
                break;
            }
            case DIRECTORY: {
                FileUtil.mkdir(resourceFile);
                break;
            }
        }

        return resource;





    }

    /**
     * 将内容写入到目标文件中，文件不存在则新建一个（覆盖模式）
     * @param springFilePath 目标文件
     * @param content 文件内容
     * @return
     */
    @SneakyThrows
    public static File writeContentToFile(String springFilePath, String content) {
        synchronized (springFilePath) {
            if(StrUtil.isBlank(springFilePath)) {
                return null;
            }
            Resource resource = noExistToTouch(springFilePath, FileTypeEnum.TEXT_FILE);
            File resourceFile = resource.getFile();
            FileUtil.writeUtf8String(content, resourceFile);
            return resourceFile;
        }
    }

    /**
     * 读取Spring文件地址形式的文件
     * 仅取第一个存在的有效文件
     *
     * @param springFilePath  Spring格式的Path路径 参考{@link ResourceUtils#CLASSPATH_URL_PREFIX}
     * @return
     */
    @SneakyThrows
    public static String readSpringFileContent(String springFilePath) {
        return readSpringFileContent(CollUtil.newArrayList(springFilePath));
    }


    /**
     * 读取Spring文件地址形式的文件
     * 仅取第一个存在的有效文件
     * @param springFilePaths Spring格式的Path路径 参考{@link ResourceUtils#CLASSPATH_URL_PREFIX}
     * @return
     */
    @SneakyThrows
    public static String readSpringFileContent(List<String> springFilePaths) {

        Resource resultResource = CollUtil.emptyIfNull(springFilePaths)
                .stream()
                .filter(StrUtil::isNotBlank)
                .map(defaultResourceLoader::getResource)
                .filter(Resource::exists)
                .findFirst()
                .orElse(null);

        //含有生效的文件
        if(resultResource != null) {
            InputStream inputStream = resultResource.getInputStream();
            String inputStreamContent = IoUtil.read(inputStream, CharsetUtil.CHARSET_UTF_8);
            return inputStreamContent;
        }

        //无生效的文件
        return null;
    }
}
